/*
 * Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.lidroid.xutils.view;

import android.os.Build;
import android.support.v7.widget.AppCompatButton;
import android.view.View;
import android.widget.Button;

import com.lidroid.xutils.util.LogUtils;
import com.lidroid.xutils.util.DoubleKeyValueMap;
import com.lidroid.xutils.view.annotation.event.EventBase;

import java.lang.annotation.Annotation;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;

public class EventListenerManager {
	
	private EventListenerManager() {
	}
	
	/**
	 * k1: viewInjectInfo
	 * k2: interface Type
	 * value: listener
	 */
	private final static DoubleKeyValueMap<ViewInjectInfo, Class<?>, Object> listenerCache = new DoubleKeyValueMap<ViewInjectInfo, Class<?>, Object>();
	
	public static void addEventMethod(ViewFinder finder, ViewInjectInfo info, Annotation eventAnnotation,
			Object handler, Method method) {
		try {
			View view = finder.findViewByInfo(info);
			if (view != null) {
				EventBase eventBase = eventAnnotation.annotationType().getAnnotation(EventBase.class);
				Class<?> listenerType = eventBase.listenerType();
				String listenerSetter = eventBase.listenerSetter();
				String methodName = eventBase.methodName();
				
				boolean addNewMethod = false;
				Object listener = listenerCache.get(info, listenerType);
				DynamicHandler dynamicHandler = null;
				if (listener != null) {
					dynamicHandler = (DynamicHandler) Proxy.getInvocationHandler(listener);
					addNewMethod = handler.equals(dynamicHandler.getHandler());
					if (addNewMethod) {
						dynamicHandler.addMethod(methodName, method);
					}
				}
				if (!addNewMethod) {
					if (eventBase.withParams()) {
						dynamicHandler = new DynamicHandler(handler);
					} else {
						dynamicHandler = new NoParamsDynamicHandler(handler);
					}
					dynamicHandler.addMethod(methodName, method);
					listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[] { listenerType },
							dynamicHandler);
							
					listenerCache.put(info, listenerType, listener);
				}
				
				/*
				 * Check if viewClazz is child class of AppCompatButton.
				 * It is a little strange but true that, button declared in XML
				 * will be transformed into AppCompatButton when project using
				 * AppCompat library. And methods with name like getXXXMethods() of
				 * AppCompatButton.class are not available in 2.3.X.
				 * Here check the version and class,in the special condition 
				 * using Button to replace it.It works in 4.1.1,but just in
				 * case let's do it when it is under 4.2(JELLY_BEAN_MR1)
				 * by DrkCore on 2015-11-21
				 */
				Class<?> viewClazz = view.getClass();
				if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1
						&& AppCompatButton.class.isAssignableFrom(viewClazz)) {
					viewClazz = Button.class;
				}
				Method setEventListenerMethod = viewClazz.getMethod(listenerSetter, listenerType);
				setEventListenerMethod.invoke(view, listener);
			}
		} catch (Throwable e) {
			LogUtils.e(e.getMessage(), e);
		}
	}
	
	public static class DynamicHandler implements InvocationHandler {
		protected WeakReference<Object> handlerRef;
		protected final HashMap<String, Method> methodMap = new HashMap<String, Method>(1);
		
		public DynamicHandler(Object handler) {
			this.handlerRef = new WeakReference<Object>(handler);
		}
		
		public void addMethod(String name, Method method) {
			methodMap.put(name, method);
		}
		
		public Object getHandler() {
			return handlerRef.get();
		}
		
		public void setHandler(Object handler) {
			this.handlerRef = new WeakReference<Object>(handler);
		}
		
		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			Object handler = handlerRef.get();
			if (handler != null) {
				String methodName = method.getName();
				method = methodMap.get(methodName);
				if (method != null) {
					return method.invoke(handler, args);
				}
			}
			return null;
		}
	}
	
	/**
	 * 每次都只调用无参数方法的InvocationHandler实现类。
	 * 
	 * @author DrkCore
	 * @since 2015年10月7日17:37:20
	 */
	public static class NoParamsDynamicHandler extends DynamicHandler {
		
		private static final Object[] NO_PARAMS = new Object[0];
		
		public NoParamsDynamicHandler(Object handler) {
			super(handler);
		}
		
		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			Object handler = handlerRef.get();
			if (handler != null) {
				String methodName = method.getName();
				method = methodMap.get(methodName);
				if (method != null) {
					return method.invoke(handler, NO_PARAMS);
				}
			}
			return null;
		}
	}
}
